﻿using System;
using System.Collections.Generic;
#if DEBUG
using System.Diagnostics;
#endif // #if DEBUG
using System.Linq;
using System.Net;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Threading;

using FRL.DebtChased.Extensions;
using FRL.DebtChased.Silverlight.Library;
using FRL.DebtChased.WorkflowObjects;
using FRL.DebtChased.WorkflowEditor.WorkflowItemEditors;

namespace FRL.DebtChased.WorkflowEditor
{

	/// <summary>
	/// Declaration of PlannerCanvas class
	/// </summary>
	public partial class PlannerCanvas : Canvas
	{

		/////////////////////////////////////////////////////////////
		// Nested classes

		public class Slot
		{

			/////////////////////////////////////////////////////////
			// Attributes

			public int Day { get; set; }
			public int Offset { get; set; }

		}

		/////////////////////////////////////////////////////////////
		// Enumerations

		private enum eDragMode
		{
			NotSet = 0,
			DraggingBoing,
			DraggingNewBoing,
		}

		/////////////////////////////////////////////////////////////
		// Constants

		public const double RowHeight = 36 + 6;

		/////////////////////////////////////////////////////////////
		// Attributes

		private eDragMode m_dragMode = eDragMode.NotSet;
		private ActionBoingCtrl m_dragBoing = null;

		private Size m_sizeDragCanvasOffset = Size.Empty;

		private static Brush m_RowBackgroundBrush;
		private static Brush m_RowBottomLineBrush;

		private bool m_bMouseDown = false;
		private int m_iLastMouseDownTick;
		private Point m_ptMouseDownBoing;
		private Point m_szMouseDragBoingOffset;
		private int m_iDragOriginalRowIndex = 0;

		private ActionBoingCtrl m_selected = null;
		public bool IsActionSelected
		{
			get { return (m_selected != null); }
		}

		WorkflowEditingCtrl m_parentCtrl = null;

		/////////////////////////////////////////////////////////////
		// Construction

		static PlannerCanvas()
		{

			// Create brushes
			m_RowBackgroundBrush = new SolidColorBrush(Colors.White);
			m_RowBottomLineBrush = new SolidColorBrush(Color.FromArgb(0xFF, 0xee, 0xee, 0xee));

		}

		public PlannerCanvas()
		{

			// Initialize resources
			InitializeComponent();

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		/////////////////////////////////////////////////////////////
		// Event handlers

		private void gridPlanner_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
#if DEBUG
			Debug.Assert(!m_bMouseDown);
#endif // #if DEBUG

			// Get point on grid
			Point ptGrid = e.GetPosition(gridPlanner);

			// Confirm the offsets are know
			_lowLevel_EnteringCoordFunction(ptGrid, e);

			// Update the selection
			if (_lowLevel_updateSelection(null))
				_getParentCtrl().SelectionChanged(m_selected != null);

			// Update flag
			m_bMouseDown = true;

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		private void gridPlanner_MouseMove(object sender, MouseEventArgs e)
		{

			// If not dragging, something is selected, and mouse is down...
			if (
				(m_dragMode == eDragMode.NotSet)
				&&
				(m_selected!=null)
				&&
				(m_bMouseDown)
			)
			{

				// Get point on grid
				Point ptGrid = e.GetPosition(gridPlanner);

				// Confirm the offsets are know
				_lowLevel_EnteringCoordFunction(ptGrid, e);

				// Calculate shift distance
				double dblDistance = m_ptMouseDownBoing.CalcDistance(ptGrid);
				if (dblDistance >= 10)
				{

					// Start a drag
#if DEBUG
					Debug.WriteLine("Start a boing drag");
#endif // #if DEBUG

					// Retain the original row
					m_iDragOriginalRowIndex = (int)Math.Floor(ptGrid.Y / RowHeight);
#if DEBUG
					Debug.WriteLine("Original row: {0}",m_iDragOriginalRowIndex);
#endif // #if DEBUG

					// Calculate center of boing
					m_szMouseDragBoingOffset = new Point(0, 0);
#if DEBUG
					Debug.WriteLine("Drag shape offset {0}  {1}", 
						m_szMouseDragBoingOffset.X,
						m_szMouseDragBoingOffset.Y
					);
#endif // #if DEBUG

					// If the control key is down...
					if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
					{

						// Create a boing for the action
						ActionBoingCtrl duplicateBoing = new ActionBoingCtrl();
						duplicateBoing.SetUnderlyingAction(WorkflowAction_Base.Clone(m_selected.UnderlyingAction,true), false);

						// Start a drag
						_lowLevel_startDragging(duplicateBoing, true);

					}
					else
					{

						// Start a drag
						_lowLevel_startDragging(m_selected, false);

					}

				}

			}

			// If dragging...
			if (
				(m_dragMode == eDragMode.DraggingBoing)
				||
				(m_dragMode == eDragMode.DraggingNewBoing)
			)
			{

				// Get point on grid
				Point ptGrid = e.GetPosition(gridPlanner);

				// Confirm the offsets are know
				_lowLevel_EnteringCoordFunction(ptGrid, e);

				// Move the boing
				Point ptBoing = new Point(
					ptGrid.X + m_sizeDragCanvasOffset.Width - ActionBoingCtrl.DragCenterX - m_szMouseDragBoingOffset.X,
					ptGrid.Y + m_sizeDragCanvasOffset.Height - ActionBoingCtrl.DragCenterY - m_szMouseDragBoingOffset.Y - _getParentCtrl().scrollViewer.VerticalOffset
				);
				Canvas.SetLeft(m_dragBoing, ptBoing.X);
				Canvas.SetTop(m_dragBoing, ptBoing.Y);

				// If a new boing, show the plus sign
				if (m_dragMode == eDragMode.DraggingNewBoing)
				{
					Canvas.SetLeft(_getParentCtrl().addingMarker, ptBoing.X - 12);
					Canvas.SetTop(_getParentCtrl().addingMarker, ptBoing.Y - 12);
					if (_getParentCtrl().addingMarker.Visibility != System.Windows.Visibility.Visible)
						_getParentCtrl().addingMarker.Visibility = System.Windows.Visibility.Visible;
				}

				// Get the hit position
				int iDay, iOffset;
				bool bIntraDayInsert;
				if (_lowLevel_getHitSlot(ptGrid, out iDay, out iOffset, out bIntraDayInsert))
				{

					// Show the drop marker and hide no-drop
					_lowLevel_showDropMarker(iDay, iOffset, bIntraDayInsert);
					_lowLevel_showNoDropMarker(false);

				}
				else
				{

					// Hide the drop marker, and show no-drop
					_lowLevel_hideDropMarkers();
					_lowLevel_showNoDropMarker(true);

				}

			}

		}

		private void gridPlanner_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
		{

			// If mouse was down on another window...
			if (!m_bMouseDown)
				return;

			// Update flag
			m_bMouseDown = false;

			// Debug message
#if DEBUG
			Debug.WriteLine("Planner: Left mouse button UP");
#endif // #if DEBUG

			// If dragging...
			if (
				(m_dragMode == eDragMode.DraggingBoing)
				||
				(m_dragMode == eDragMode.DraggingNewBoing)
			)
			{

				// Get point on grid
				Point ptGrid = e.GetPosition(gridPlanner);

				// Confirm the offsets are know
				_lowLevel_EnteringCoordFunction(ptGrid, e);

				// Hide the drag canvas and release mouse capture
				_getParentCtrl().dragCanvas.Visibility = System.Windows.Visibility.Collapsed;
				gridPlanner.ReleaseMouseCapture();

				// Remove from the dragging canvas
				_getParentCtrl().dragCanvas.Children.Remove(m_dragBoing);

				// Call function to handle dropping
				_dropOntoPlanner(ptGrid);

			}

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		void ActionBoing_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
		{
#if DEBUG
			Debug.Assert(!m_bMouseDown);
#endif // #if DEBUG

			// Debug dump
#if DEBUG
			Debug.WriteLine("ActionBoing_MouseLeftButtonDown");
#endif // #if DEBUG

			// Get point on grid
			Point ptGrid = e.GetPosition(gridPlanner);

			// Confirm the offsets are know
			_lowLevel_EnteringCoordFunction(ptGrid, e);

			// Update flag
			m_bMouseDown = true;

			// Get the action-boing, and select it
			ActionBoingCtrl hitBoing = sender as ActionBoingCtrl;
			if (_lowLevel_updateSelection(hitBoing))
				_getParentCtrl().SelectionChanged(m_selected != null);

			// Check for double click
			if (Environment.TickCount < (m_iLastMouseDownTick + 200))
			{
#if DEBUG
				Debug.Assert(m_selected != null);
#endif // #if DEBUG

				// Reset the mouse-down flag
				m_bMouseDown = false;

				// Edit the boing
				ActionBoingPropertiesWnd wnd = new ActionBoingPropertiesWnd(m_selected.UnderlyingAction);
				wnd.Closed += new EventHandler(ActionBoingPropertiesWnd_Closed);
				wnd.Show();

			}
			else
			{

				// Retain the click time and hit point
				m_iLastMouseDownTick = Environment.TickCount;
				m_ptMouseDownBoing = e.GetPosition(gridPlanner);

			}

			// Prevent message bubbling up
			e.Handled = true;

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		void ActionBoingPropertiesWnd_Closed(object sender, EventArgs e)
		{
#if DEBUG
			Debug.Assert(m_selected != null);
#endif // #if DEBUG

			// Update the edited boing
			m_selected.SetUnderlyingAction(m_selected.UnderlyingAction, false);

			// May need to create more days
			int iRowIndex = Grid.GetRow(m_selected);
#if DEBUG
			Debug.Assert(iRowIndex != -1);
#endif // #if DEBUG
			Slot slot = _lowLevel_getSlotAtRowIndex(iRowIndex);
			int iDaysRequired = slot.Day + m_selected.UnderlyingAction.DaySpan - 1;
			int iExtraDays = iDaysRequired - _lowLevel_calcTotalDays();
			if (iExtraDays > 0)
			{
				while (iExtraDays > 0)
				{
					_lowLevel_addDay();
					iExtraDays--;
				}
				_lowLevel_updatePlannerGridHeight();
			}

			// Update controls (may no longer be able to dec rows);
			_updateControlsAfterTotalDaysChange();

			// Recalc the layout
			_lowLevel_applyIndentationAndLegLengths();

		}

		/////////////////////////////////////////////////////////////
		// Operations

		private WorkflowEditingCtrl _getParentCtrl()
		{

			// If parent not known...
			if (m_parentCtrl == null)
			{
				m_parentCtrl = this.FindParent(typeof(WorkflowEditingCtrl)) as WorkflowEditingCtrl;
#if DEBUG
				Debug.Assert(m_parentCtrl != null);
#endif // #if DEBUG
			}

			// Return parent refernece
			return m_parentCtrl;

		}

		public int GetTotalDays()
		{

			// Return day-count
			return _lowLevel_calcTotalDays();

		}

		private void _dropOntoPlanner(Point ptGrid)
		{
#if DEBUG
			Debug.Assert(m_dragBoing != null);
			Debug.Assert(m_dragMode != eDragMode.NotSet);
			Debug.Assert(_getParentCtrl().dragCanvas.Visibility == System.Windows.Visibility.Collapsed);
			Debug.Assert(!_getParentCtrl().dragCanvas.Children.Contains(m_dragBoing));
#endif // #if DEBUG

			// Declaration of local variables
			Slot dragSlot = null;

			// Flag for replacing boing where it was
			bool bPutBackInOriginalPosition = false;

			// Calculate the total days
			int iTotalDays = _lowLevel_calcTotalDays();

			// Get the hit position
			int iDay, iOffset;
			bool bIntraDayInsert;
			if (_lowLevel_getHitSlot(ptGrid, out iDay, out iOffset, out bIntraDayInsert))
			{

				// Debug message
#if DEBUG
				Debug.WriteLine("Planner: Dropping dragged object, day={0}, offset={1}", iDay, iOffset);
#endif // #if DEBUG

				// Check for drags that don't move anywhere
				int iRowByMath = (int)Math.Floor(ptGrid.Y / RowHeight);
				if(m_dragMode == eDragMode.DraggingBoing)
				{

					// If on same row...
					if (m_iDragOriginalRowIndex == iRowByMath)
						bPutBackInOriginalPosition = true;
					else
					{

						// Check for trying to put after upper item on same day
						if (
							(bIntraDayInsert)
							&&
							(m_iDragOriginalRowIndex == (iRowByMath + 1))
						)
						{

							// Get the slot we are dragging from
							if(dragSlot==null)
								dragSlot = _lowLevel_getSlotAtRowIndex(m_iDragOriginalRowIndex);

							// Check the days
							if (dragSlot.Offset > 0)
								bPutBackInOriginalPosition = true;

						}

					}

				}

				// If not replacing...
				if(!bPutBackInOriginalPosition)
				{

					// Prepare boing for dropping into grid
					gridPlanner.Children.Add(m_dragBoing);
					m_dragBoing.Opacity = 1.0;
					if (m_dragMode == eDragMode.DraggingNewBoing)
						m_dragBoing.MouseLeftButtonDown += new MouseButtonEventHandler(ActionBoing_MouseLeftButtonDown);

					// if need to add days...
					int iDaysRequired = iDay + m_dragBoing.UnderlyingAction.DaySpan - 1;
					int iExtraDays = iDaysRequired - iTotalDays;
					while (iExtraDays > 0)
					{
						_lowLevel_addDay();
						iExtraDays--;
						iTotalDays++;
					}

					// Get row index
					int iRowIndex = _lowLevel_findRowIndexBySlot(iDay, iOffset);
#if DEBUG
					Debug.Assert(iRowIndex != -1);
#endif // #if DEBUG

					// If the drop slot is empty...
					if (_lowLevel_isRowEmpty(iRowIndex))
					{

						// Insert boing into this row							
						Grid.SetColumn(m_dragBoing, 1);
						Grid.SetRow(m_dragBoing, iRowIndex);

					}
					else
					{

						// Create a new offset-row below the row
						_lowLevel_addDaySlot(iRowIndex + 1);

						// Increment the original row
						if(m_iDragOriginalRowIndex >= (iRowIndex+1))
							m_iDragOriginalRowIndex++;

						// If adding on top...
						if (!bIntraDayInsert)
						{

							// Move boing from upper row to new row
							ActionBoingCtrl upperBoing = _lowLevel_getBoingAtRowIndex(iRowIndex);
							Grid.SetRow(upperBoing, iRowIndex + 1);

							// Insert boing into upper row
							Grid.SetColumn(m_dragBoing, 1);
							Grid.SetRow(m_dragBoing, iRowIndex);

						}
						else
						{

							// Insert boing into this row
							Grid.SetColumn(m_dragBoing, 1);
							Grid.SetRow(m_dragBoing, iRowIndex + 1);

						}

					}

					// If the original row-index is empty...
					if (
						(m_dragMode == eDragMode.DraggingBoing) 
						&& 
						(_lowLevel_isRowEmpty(m_iDragOriginalRowIndex))
					)
					{

						// If this row has siblings...
						if (_lowLevel_hasSiblingRows(m_iDragOriginalRowIndex))
							_lowLevel_removeRow(m_iDragOriginalRowIndex);

					}

					// Recalc layout (indentation and leg lengths)
					_lowLevel_updatePlannerGridHeight();
					_lowLevel_applyIndentationAndLegLengths();

					// Update the control
					_updateControlsAfterTotalDaysChange();

					// This actgion should be selected
					if (_lowLevel_updateSelection(m_dragBoing))
						_getParentCtrl().SelectionChanged(m_selected != null);

				}

			}
			else
			{

				// Debug message
#if DEBUG
				Debug.WriteLine("Planner: NO-ENTRY Drop");
#endif // #if DEBUG

				// If dragging an existing action...
				if (m_dragMode == eDragMode.DraggingBoing)
					bPutBackInOriginalPosition = true;

			}

			// If replacing boing...
			if (bPutBackInOriginalPosition)
			{

				// Prepare boing for dropping into grid
				gridPlanner.Children.Add(m_dragBoing);
				m_dragBoing.Opacity = 1.0;
				Grid.SetColumn(m_dragBoing, 1);
				Grid.SetRow(m_dragBoing, m_iDragOriginalRowIndex);

			}

			// Release dragging members
			m_dragMode = eDragMode.NotSet;
			m_dragBoing = null;

		}

		public void StartNewDrag(ActionBoingCtrl newBoing)
		{

			// Debug message
#if DEBUG
			Debug.WriteLine("Planner: StartNewDrag");
#endif // #if DEBUG

			// Reset the drag offset for new boings
			m_szMouseDragBoingOffset = new Point(0, 0);

			// The mouse is down
			m_bMouseDown = true;

			// Initialize drag members
			_lowLevel_startDragging(newBoing, true);

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		public void InsertActionAtDay(ActionBoingCtrl newAction, int iDay)
		{

			// Prepare boing for dropping into grid
			gridPlanner.Children.Add(newAction);
			newAction.MouseLeftButtonDown += new MouseButtonEventHandler(ActionBoing_MouseLeftButtonDown);

			// if need to add days...
			int iDaysRequired = iDay + newAction.UnderlyingAction.DaySpan - 1;
			int iExtraDays = iDaysRequired - _lowLevel_calcTotalDays();
			while (iExtraDays > 0)
			{
				_lowLevel_addDay();
				iExtraDays--;
			}

			// Find rows for day
			int iRowIndex, iRowCount;
			_lowLevel_findRowsForDay(iDay,out iRowIndex, out iRowCount);
#if DEBUG
			Debug.Assert(iRowIndex != -1);
#endif // #if DEBUG

			// If this day is empty...
			if (_lowLevel_isDayEmpty(iDay))
			{

				// Add to grid
				Grid.SetRow(newAction,iRowIndex);
				Grid.SetColumn(newAction, 1);

			}
			else
			{

				// Insert into offset slot
				_lowLevel_addDaySlot(iRowIndex + iRowCount);

				// Insert into day
				Grid.SetRow(newAction, iRowIndex + iRowCount);
				Grid.SetColumn(newAction, 1);

			}

			// Update the layout
			_lowLevel_updatePlannerGridHeight();
			_lowLevel_applyIndentationAndLegLengths();
			_updateControlsAfterTotalDaysChange();
			if (_lowLevel_updateSelection(newAction))
				_getParentCtrl().SelectionChanged(m_selected != null);

		}

		public void IncDays(bool bIncDays, int iCount=1)
		{

			// If adding a day...
			if (bIncDays)
			{

				// add a new day
				while(iCount>0)
				{
					_lowLevel_addDay();
					iCount--;
				}

				// adjust grid height
				_lowLevel_updatePlannerGridHeight();

				// post days change update
				_updateControlsAfterTotalDaysChange();

				// Scroll into view
				_lowLevel_scrollRowIntoView(gridPlanner.RowDefinitions.Count - 1);

			}
			else
			{

				// Loop through count
				while (iCount > 0)
				{

					// Get the total number of days
					int iTotalDays = this._lowLevel_calcTotalDays();

					// Get all the rows for this day
					int iRowIndex;
					int iRowCount;
					_lowLevel_findRowsForDay(iTotalDays, out iRowIndex, out iRowCount);

					// Get controls for these rows
					List<FrameworkElement> list = _lowLevel_findControlsInRow(iRowIndex, iRowCount, true);

					// Remove the controls in the final row
					while (list.Count > 0)
					{
						gridPlanner.Children.Remove(list[0]);
						list.RemoveAt(0);
					}

					// Remove the rows
					while (iRowCount > 0)
					{
						gridPlanner.RowDefinitions.RemoveAt(gridPlanner.RowDefinitions.Count - 1);
						iRowCount--;
					}

					// Move on
					iCount--;

				}

				// adjust grid height
				_lowLevel_updatePlannerGridHeight();

				// post days change update
				_updateControlsAfterTotalDaysChange();

				// Update the shape row-span
				_lowLevel_managePayDayMarker();

			}

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		public void EditSelected()
		{

			// If an action is selected...
			if (m_selected != null)
			{

				// Edit the boing
				ActionBoingPropertiesWnd wnd = new ActionBoingPropertiesWnd(m_selected.UnderlyingAction);
				wnd.Closed += new EventHandler(ActionBoingPropertiesWnd_Closed);
				wnd.Show();

			}

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		public void DeleteSelected()
		{

			// Shortcuts
			if (m_selected == null)
			{
#if DEBUG
				Debug.Assert(false);
#endif // #if DEBUG
				return;
			}

			// Get the row index of this action
			int iRowIndex = Grid.GetRow(m_selected);

			// Get the slot
			Slot slot = _lowLevel_getSlotAtRowIndex(iRowIndex);

			// Remove the action
			gridPlanner.Children.Remove(m_selected);

			// If the original row-index is empty...
			if (_lowLevel_isRowEmpty(iRowIndex))
			{

				// If this row has siblings...
				if (_lowLevel_hasSiblingRows(iRowIndex))
					_lowLevel_removeRow(iRowIndex);

			}

			// Recalc layout (indentation and leg lengths)
			_lowLevel_updatePlannerGridHeight();
			_lowLevel_applyIndentationAndLegLengths();

			// Update the control
			_updateControlsAfterTotalDaysChange();

			// Reset the selection
			if (_lowLevel_updateSelection(null))
				_getParentCtrl().SelectionChanged(m_selected != null);

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		public void ClearSelection()
		{

			// Clear selection
			if (_lowLevel_updateSelection(null))
				_getParentCtrl().SelectionChanged(m_selected != null);

		}

		private void _updateControlsAfterTotalDaysChange()
		{

			// Get the total number of days
			int iTotalDays = this._lowLevel_calcTotalDays();

			// Can the bottom row be removed
			bool bCanDeleteBottomDay = false;
			if (iTotalDays > 1)
			{

				// Get the map for optomistic loop
				bCanDeleteBottomDay = true;
				Dictionary<Slot,ActionBoingCtrl> map = _lowLevel_getSlotActionMap();
				foreach( var di in map)
				{

					// If slot has an action...
					if (di.Value != null)
					{

						// Check for object in day
						int iBottomDay = di.Key.Day + di.Value.UnderlyingAction.DaySpan - 1;
#if DEBUG
						Debug.Assert(iBottomDay <= iTotalDays);
#endif // #if DEBUG
						if (iBottomDay == iTotalDays)
						{
							bCanDeleteBottomDay = false;
							break;
						}

					}

				}
	
			}

			// Update the main page controls
			_getParentCtrl().UpdateDaysAndPayDayAfterChange(iTotalDays,bCanDeleteBottomDay);

		}

		public void MovePayDayMarker(int iPayDay, bool bAnimate)
		{

			// Calculate new Y offset
			double dblYOffset = ((RowHeight - shapePayDay.Height) / 2) + ((iPayDay - 1) * RowHeight);

			// If animating...
			if (bAnimate)
			{

				// Move to position
				animMovePayDayMarker.To = dblYOffset;
				storyMovePayDayMarker.Begin();

			}
			else
			{

				// Set position
				transformPayDay.Y = dblYOffset;

			}

		}

		public void SetWorkflow(Workflow workflow, bool bIsInstalmentStatus)
		{

			// Clear the existing content
			gridPlanner.RowDefinitions.Clear();
			gridPlanner.Children.Clear();
			m_selected = null;
			m_dragMode = eDragMode.NotSet;
			m_dragBoing = null;

			// Sort the workflow actions
#if DEBUG
			workflow.Actions.Reverse();
#endif //#if DEBUG
			workflow.SortActions();

			// Create the days
			int iDaysRequired = workflow.Days;
			for (int i1 = 0; i1 < workflow.Actions.Count; i1++)
				iDaysRequired = Math.Max(iDaysRequired, workflow.Actions[i1].Day + workflow.Actions[i1].DaySpan - 1);
			while (iDaysRequired > 0)
			{
				_lowLevel_addDay();
				iDaysRequired--;
			}
			int iTotalDays = _lowLevel_calcTotalDays();

			// Show/hide the pay-day marker
			shapePayDay.Visibility = bIsInstalmentStatus ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;

			// Loop through the actions
			for( int i1=0; i1<workflow.Actions.Count; i1++ )
			{

				// Get the next action
				WorkflowAction_Base action = workflow.Actions[i1];

				// Find the row index
				int iRowIndex, iRowCount;
				_lowLevel_findRowsForDay(action.Day, out iRowIndex, out iRowCount);
#if DEBUG
				Debug.Assert(iRowIndex != -1);
#endif // #if DEBUG

				// If this action is for a day slot...
				if (action.Offset == 0)
				{
#if DEBUG
					Debug.Assert(_lowLevel_isRowEmpty(iRowIndex));
#endif // #if DEBUG

					// Insert into day
					ActionBoingCtrl newAction = new ActionBoingCtrl();
					newAction.SetUnderlyingAction(action,false);
					gridPlanner.Children.Add(newAction);
					Grid.SetRow(newAction, iRowIndex);
					Grid.SetColumn(newAction, 1);
					newAction.MouseLeftButtonDown += new MouseButtonEventHandler(ActionBoing_MouseLeftButtonDown);

					// Check new position
#if DEBUG
					Debug.Assert(_lowLevel_getSlotAtRowIndex(iRowIndex).Day == newAction.UnderlyingAction.Day);
					Debug.Assert(_lowLevel_getSlotAtRowIndex(iRowIndex).Offset == newAction.UnderlyingAction.Offset);
#endif // #if DEBUG

				}
				else
				{
#if DEBUG
					Debug.Assert(!_lowLevel_isRowEmpty(iRowIndex));
#endif // #if DEBUG

					// Insert into offset slot
					_lowLevel_addDaySlot(iRowIndex + iRowCount);

					// Insert into day
					ActionBoingCtrl newAction = new ActionBoingCtrl();
					newAction.SetUnderlyingAction(action, false);
					gridPlanner.Children.Add(newAction);
					Grid.SetRow(newAction, iRowIndex + iRowCount);
					Grid.SetColumn(newAction, 1);
					newAction.MouseLeftButtonDown += new MouseButtonEventHandler(ActionBoing_MouseLeftButtonDown);

					// Check new position
#if DEBUG
					Debug.Assert(_lowLevel_getSlotAtRowIndex(iRowIndex + iRowCount).Day == newAction.UnderlyingAction.Day);
					Debug.Assert(_lowLevel_getSlotAtRowIndex(iRowIndex + iRowCount).Offset == newAction.UnderlyingAction.Offset);
#endif // #if DEBUG

				}

			}

			// Update the layout
			_lowLevel_updatePlannerGridHeight();
			_lowLevel_applyIndentationAndLegLengths();
			_updateControlsAfterTotalDaysChange();

			// Validate content
#if DEBUG
			_lowLevel_assertValidContent();
#endif // #if DEBUG

		}

		public void GetWorkflow(Workflow wf)
		{
#if DEBUG
			Debug.Assert(wf.m_actions.Count == 0);
#endif // #if DEBUG

			// Update the total days
			wf.Days = _lowLevel_calcTotalDays();

			// Get the action map
			Dictionary<Slot,ActionBoingCtrl> map = _lowLevel_getSlotActionMap();

			// Loop through the map
			foreach (var di in map)
			{

				// If this slot has an action...
				if (di.Value != null)
				{

					// Create an action for the workflow
					WorkflowAction_Base action = WorkflowAction_Base.Clone(di.Value.UnderlyingAction,false);
					action.Day = di.Key.Day;
					action.Offset = di.Key.Offset;

					// Add to the workflow
					wf.Actions.Add(action);

				}

			}

		}

		/////////////////////////////////////////////////////////////
		#region Low level row operations

		private bool _lowLevel_isDayEmpty(int iDay)
		{

			// Declare return variable
			bool bFtmp = true;

			// Get the rows for this day
			int iRowIndex, iRowCount;
			_lowLevel_findRowsForDay(iDay, out iRowIndex, out iRowCount);
#if DEBUG
			Debug.Assert(iRowIndex != -1);
#endif // #if DEBUG

			// Loop through the grid children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not a framework element
				if (!(child is FrameworkElement))
					continue;
				FrameworkElement el = child as FrameworkElement;

				// Ignore if this is decoration
				if (_lowLevel_isDecoration(el))
					continue;

				// Check row and column
				int iElementRow = Grid.GetRow(el);
				if(
					(Grid.GetColumn(el)==1)
					&&
					(iElementRow >= iRowIndex)
 					&&
					(iElementRow < (iRowIndex + iRowCount))
				)
				{

					// Update return variable and break out loop
					bFtmp = false;
					break;

				}

			}

			// Return variable
			return bFtmp;

		}

		private void _lowLevel_findRowsForDay(int iDay, out int iRowIndex, out int iRowCount)
		{

			// Reset function parameters
			iRowIndex = 0;
			iRowCount = 0;

			// Loop through the grid children
			int iMinRowIndex = -1;
			int iMaxRowIndex = -1;
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not a grid
				if (!(child is Grid))
					continue;
				Grid childGrid = child as Grid;

				// If this child is a grid in column 0...
				if (
					(Grid.GetColumn(childGrid) == 0)
					&&
					(childGrid.Tag != null)
					&&
					(childGrid.Tag is Slot)
				)
				{

					// Get the slot
					Slot slot = childGrid.Tag as Slot;

					// If slot matches filter...
					if (slot.Day == iDay)
					{

						// Update function parameters
						int iRow = Grid.GetRow(childGrid);
						if (iMinRowIndex == -1)
						{
							iMinRowIndex = iRow;
							iMaxRowIndex = iRow;
						}
						else
						{
							iMinRowIndex = Math.Min(iMinRowIndex, iRow);
							iMaxRowIndex = Math.Max(iMaxRowIndex, iRow);
						}

					}

				}

			}

			// Update function parameters
			iRowIndex = iMinRowIndex;
			iRowCount = iMaxRowIndex - iRowIndex + 1;

		}

		private bool _lowLevel_isDecoration(FrameworkElement el)
		{

			// Text the element
			return (
				(
					(el is Grid)
					&&
					((el as Grid).Tag is Slot)
				)
				||
				(el is Rectangle)
			);

		}

		private int _lowLevel_findRowIndexBySlot(int iDay, int iOffset)
		{

			// Declare return variable
			int iFtmp = -1;

			// Loop through the children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore anything that isn't a day-grid
				if (!(child is Grid))
					continue;
				Grid grid = child as Grid;

				// Day grid has a tag and is in column 0
				if(
					(Grid.GetColumn(grid) == 0)
					&&
					(grid.Tag != null)
					&&
					(grid.Tag is Slot)
				)
				{

					// If slot matches...
					Slot slot = grid.Tag as Slot;
					if ((slot.Day == iDay) && (slot.Offset == iOffset))
					{

						// Update return variable and break
						iFtmp = Grid.GetRow(grid);
						break;

					}

				}
				

			} // foreach loop

			// Return variable
			return iFtmp;

		}

		private Slot _lowLevel_getSlotAtRowIndex(int iRowIndex)
		{

			// Declare return variable
			Slot Ftmp = null;

			// Loop through the grid children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore anything that isn't a framework element
				if (!(child is FrameworkElement))
					continue;
				FrameworkElement el = child as FrameworkElement;

				// Look for  grid in column 0...
				if (
					(el is Grid)
					&&
					(Grid.GetColumn(el) == 0)
					&&
					(Grid.GetRow(el) == iRowIndex)
				)
				{

					// Update return variable and break
					Ftmp = (el as Grid).Tag as Slot;
					break;

				}

			}

			// Return variable
			return Ftmp;

		}

		private ActionBoingCtrl _lowLevel_getBoingAtRowIndex(int iRowIndex)
		{

			// Declare return variable
			ActionBoingCtrl Ftmp = null;

			// Loop through the grid children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore anything that isn't a framework element
				if (!(child is FrameworkElement))
					continue;
				FrameworkElement el = child as FrameworkElement;

				// Look for content in column 1...
				if (
					(el is ActionBoingCtrl)
					&&
					(Grid.GetColumn(el) == 1)
					&&
					(Grid.GetRow(el) == iRowIndex)
				)
				{

					// Update return variable and break
					Ftmp = el as ActionBoingCtrl;
					break;

				}

			}

			// Return variable
			return Ftmp;

		}

		private bool _lowLevel_isRowEmpty(int iRowIndex)
		{

			// Declare return variable
			bool bFtmp = true;

			// Loop through all children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore anything that isn't a framework element
				if (!(child is FrameworkElement))
					continue;
				if (child == shapePayDay)
					continue;
				FrameworkElement el = child as FrameworkElement;

				// Look for non-decoration object in column 1
				if (
					(Grid.GetColumn(el) == 1)
					&&
					(Grid.GetRow(el) == iRowIndex)
					&&
					(!_lowLevel_isDecoration(el))
				)
				{

					// Update return variable and break
					bFtmp = false;
					break;

				}

			}

			// Return variable
			return bFtmp;

		}

		private void _lowLevel_addDay()
		{

			// Get the total number of days
			int iTotalDays = this._lowLevel_calcTotalDays();

			// Create a new slot
			Slot slot = new Slot();
			slot.Day = iTotalDays + 1;
			slot.Offset = 0;

			// Create decorations
			Grid gridDay;
			Rectangle rectBackground;
			Rectangle rectBottomLine;
			_lowLevel_createDecorations(slot, out gridDay, out rectBackground, out rectBottomLine);

			// Create a row in the planner, add the day and bottom line
			RowDefinition newRow = new RowDefinition();
			newRow.Height = new GridLength(RowHeight);

			// Add child at the back of the list
			gridPlanner.Children.Insert(0,rectBackground);
			Grid.SetColumn(rectBackground, 0);
			Grid.SetRow(rectBackground, gridPlanner.RowDefinitions.Count);
			Grid.SetColumnSpan(rectBackground, 2);

			gridPlanner.Children.Insert(1, rectBottomLine);
			Grid.SetColumn(rectBottomLine, 1);
			Grid.SetRow(rectBottomLine, gridPlanner.RowDefinitions.Count);

			gridPlanner.Children.Insert(2, gridDay);
			Grid.SetColumn(gridDay, 0);
			Grid.SetRow(gridDay, gridPlanner.RowDefinitions.Count);

			gridPlanner.RowDefinitions.Add(newRow);

			// Update the shape row-span
			_lowLevel_managePayDayMarker();

		}

		private void _lowLevel_managePayDayMarker()
		{

			// Make sure marker is on top
			if (gridPlanner.Children[gridPlanner.Children.Count - 1] != shapePayDay)
			{
				gridPlanner.Children.Remove(shapePayDay);
				gridPlanner.Children.Add(shapePayDay);
			}

			// Check the row span is all rows
			int iRow = Grid.GetRow(shapePayDay);
#if DEBUG
			Debug.Assert(iRow == 0);
#endif // #if DEBUG
			Grid.SetRowSpan(shapePayDay, gridPlanner.RowDefinitions.Count);

		}

		private void _lowLevel_addDaySlot(int iRowIndex)
		{

			// Get the day from the above slot
			Slot upperSlot = _lowLevel_getSlotAtRowIndex(iRowIndex-1);
#if DEBUG
			Debug.Assert(upperSlot != null);
#endif // #if DEBUG

			// Create a new slot
			Slot slot = new Slot();
			slot.Day = upperSlot.Day;
			slot.Offset = upperSlot.Offset + 1;

			// Create decorations
			Grid gridDay;
			Rectangle rectBackground;
			Rectangle rectBottomLine;
			_lowLevel_createDecorations(slot, out gridDay, out rectBackground, out rectBottomLine);

			// Create a row in the planner, add the day and bottom line
			RowDefinition newRow = new RowDefinition();
			newRow.Height = new GridLength(RowHeight);

			// Insert the new row
			gridPlanner.RowDefinitions.Insert(iRowIndex, newRow);

			// Increment row assignments
			_lowLevel_incRowAssignments(iRowIndex,true);

			// Now add children to the row (at the back)
			gridPlanner.Children.Insert(0,rectBackground);
			Grid.SetColumn(rectBackground, 0);
			Grid.SetRow(rectBackground, iRowIndex);
			Grid.SetColumnSpan(rectBackground, 2);

			gridPlanner.Children.Insert(1,rectBottomLine);
			Grid.SetColumn(rectBottomLine, 1);
			Grid.SetRow(rectBottomLine, iRowIndex);

			gridPlanner.Children.Insert(2,gridDay);
			Grid.SetColumn(gridDay, 0);
			Grid.SetRow(gridDay, iRowIndex);

			// Add the slots below will need their offset incrementing
			int iRowIndex2 = iRowIndex + 1;
			while (iRowIndex2 < gridPlanner.RowDefinitions.Count)
			{

				// Get the slot
				Slot slot2 = _lowLevel_getSlotAtRowIndex(iRowIndex2);
				if (slot.Day == slot2.Day)
					slot2.Offset++;
				else
					break;

				// Move on
				iRowIndex2++;

			} // while-loop

			// Update the shape row-span
			_lowLevel_managePayDayMarker();

		}

		private void _lowLevel_createDecorations( Slot slot, out Grid gridDay, out Rectangle rectBackground, out Rectangle rectBottomLine )
		{

			// Create a day box grid
			gridDay = new Grid();
			gridDay.Tag = slot;
			if (slot.Offset == 0)
			{
				gridDay.Style = (Style)Resources["DayGridStyle"];
				gridDay.AddRows(10, 26);
				Rectangle rectWhite = new Rectangle() { Style = (Style)Resources["DayWhiteCellStyle"] };
				Rectangle rectRed = new Rectangle() { Style = (Style)Resources["DayRedCellStyle"] };
				TextBlock tbDay = new TextBlock() { Text = "DAY", Style = (Style)Resources["DayLabelStyle"] };
				TextBlock tbNumber = new TextBlock() { Text = slot.Day.ToString(), Style = (Style)Resources["DayNumberStyle"] };
				Border border = new Border() { Style = (Style)Resources["DayBorderStyle"] };
				gridDay.Children.Add(rectWhite);
				gridDay.Children.Add(rectRed);
				gridDay.Children.Add(tbDay);
				gridDay.Children.Add(tbNumber);
				gridDay.Children.Add(border);
			}

			// Add rectangle background
			rectBackground = new Rectangle();
			rectBackground.Fill = m_RowBackgroundBrush;
			rectBackground.Height = RowHeight;
			rectBackground.Width = 762;
			rectBackground.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
			rectBackground.VerticalAlignment = System.Windows.VerticalAlignment.Top;

			// Add rectangle bottom line
			rectBottomLine = new Rectangle();
			rectBottomLine.Fill = m_RowBottomLineBrush;
			rectBottomLine.Height = 2;
			rectBottomLine.Width = 680;
			rectBottomLine.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
			rectBottomLine.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;

		}

		private bool _lowLevel_getHitSlot (Point ptGrid, out int iDay, out int iOffset, out bool bIntraDayInsert )
		{

			// Reset function parameters
			iDay = 1;
			iOffset = 0;
			bIntraDayInsert = false;

			// shortcuts
			if (ptGrid.X < 0)
				return false;
			if (ptGrid.X > gridPlanner.ActualWidth)
				return false;
			double dblOffsetY = ptGrid.Y - _getParentCtrl().scrollViewer.VerticalOffset;
			if (dblOffsetY < 0)
				return false;
			if (dblOffsetY > _getParentCtrl().scrollViewer.ViewportHeight)
				return false;

			// Find row index
			int iRowIndex = (int)(ptGrid.Y / RowHeight);

			// If below the bottom row...
			if (iRowIndex >= gridPlanner.RowDefinitions.Count)
			{

				// Insert at next day
				iDay = _lowLevel_calcTotalDays() + 1;
				iOffset = 0;

			}
			else
			{

				// Get the slot information for this row
				Slot slot = _lowLevel_getSlotAtRowIndex(iRowIndex);
				iDay = slot.Day;
				iOffset = slot.Offset;

				// If this slot is not empty...
				if (!_lowLevel_isRowEmpty(iRowIndex))
				{

					// If in top part of row...
					double dblHeightRatio = ((double)ptGrid.Y % RowHeight) / RowHeight;
					if (dblHeightRatio >= 0.8)
					{

						// Update function parameters
						bIntraDayInsert = true;
					}

				}

			}

			// return success
			return true;

		}

		private void _lowLevel_EnteringCoordFunction(Point ptGrid, MouseEventArgs e)
		{

			// Compensate grid point for scrolling
			ptGrid.Y -= _getParentCtrl().scrollViewer.VerticalOffset;

			// If offset between planner grid and drag canvas is not known...
			if (m_sizeDragCanvasOffset.IsEmpty)
			{
				Point ptDragCanvas = e.GetPosition(_getParentCtrl().dragCanvas);
				double x = ptDragCanvas.X - ptGrid.X;
				m_sizeDragCanvasOffset.Width = ptDragCanvas.X - ptGrid.X;
				m_sizeDragCanvasOffset.Height = ptDragCanvas.Y - ptGrid.Y;
			}

		}

		private List<FrameworkElement> _lowLevel_findControlsInRow(int iRowIndex, int iRowCount, bool bIncludeDecorations)
		{

			// Declare return variable
			List<FrameworkElement> Ftmp = new List<FrameworkElement>();

			// Loop through children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not a framework element
				if (!(child is FrameworkElement))
					continue;
				FrameworkElement el = child as FrameworkElement;

				// If this element is in the row...
				int iElementRow = Grid.GetRow(el);
				if (
					(iElementRow >= iRowIndex)
					&&
					(iElementRow < (iRowIndex + iRowCount))
				)
				{

					// If included in filter...
					if (
						(bIncludeDecorations)
						||
						(!_lowLevel_isDecoration(el))
					)
						Ftmp.Add(el);

				}

			}

			// Return variable
			return Ftmp;

		}

		private int _lowLevel_calcTotalDays()
		{

			// Declare return variable
			int iFtmp = 0;

			// Loop through the grid children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not a grid
				if (!(child is Grid))
					continue;
				Grid childGrid = child as Grid;

				// If this child is a grid in column 0...
				if (
					(Grid.GetColumn(childGrid) == 0)
					&&
					(childGrid.Tag != null)
					&&
					(childGrid.Tag is Slot)
				)
				{

					// Get the slot
					Slot slot = childGrid.Tag as Slot;

					// Update return variable
					iFtmp = Math.Max(iFtmp, slot.Day);

				}

			}

			// Return variable
			return iFtmp;

		}

		private void _lowLevel_incRowAssignments(int iRowIndex, bool bInc)
		{

			// Loop through the grid children
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not a framework element
				if (!(child is FrameworkElement))
					continue;
				FrameworkElement el = child as FrameworkElement;

				// Get the row
				int iRow = Grid.GetRow(el);
				if (iRow >= iRowIndex)
				{
					if(bInc)
						Grid.SetRow(el, iRow + 1);
					else
						Grid.SetRow(el, iRow - 1);
				}

			}

		}

		private void _lowLevel_showDropMarker(int iDay, int iOffset, bool bIntraDayInsert)
		{

			// get the row index for this
			int iRowIndex;
			if (iDay == (_lowLevel_calcTotalDays() + 1))
				iRowIndex = gridPlanner.RowDefinitions.Count;
			else
				iRowIndex = _lowLevel_findRowIndexBySlot(iDay, iOffset);

			// Calc point for drop marker (center of row)
			Point ptDropMarker = new Point(
				60 + 0,
				(RowHeight * iRowIndex) + ((RowHeight - 20) / 2)
			);
			ptDropMarker.X += m_sizeDragCanvasOffset.Width;
			ptDropMarker.Y += m_sizeDragCanvasOffset.Height;
			ptDropMarker.Y -= _getParentCtrl().scrollViewer.VerticalOffset;

			// If inserting intra-day...
			if (bIntraDayInsert)
				ptDropMarker.Y += 20;

			// Set position in drag-canvas
			if (bIntraDayInsert)
			{
				Canvas.SetLeft(_getParentCtrl().dropMarkerBottom, ptDropMarker.X);
				Canvas.SetTop(_getParentCtrl().dropMarkerBottom, ptDropMarker.Y);
			}
			else
			{
				Canvas.SetLeft(_getParentCtrl().dropMarkerCenter, ptDropMarker.X);
				Canvas.SetTop(_getParentCtrl().dropMarkerCenter, ptDropMarker.Y);
			}

			// Show the drop marker
			_getParentCtrl().dropMarkerCenter.Visibility = (!bIntraDayInsert) ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
			_getParentCtrl().dropMarkerBottom.Visibility = (bIntraDayInsert) ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;

		}

		private void _lowLevel_hideDropMarkers()
		{

			// If the drop marker is visible...
			_getParentCtrl().dropMarkerCenter.Visibility = System.Windows.Visibility.Collapsed;
			_getParentCtrl().dropMarkerBottom.Visibility = System.Windows.Visibility.Collapsed;

		}

		private void _lowLevel_showNoDropMarker(bool bShow)
		{

			// If showing...
			if (bShow)
			{

				// Position the sign over the boing
				Point ptSign = new Point(
					Canvas.GetLeft(m_dragBoing),
					Canvas.GetTop(m_dragBoing)
				);
				ptSign.X += (560 - 40) / 2;
				ptSign.Y += (48 - 40) / 2;
				ptSign.Y -= 4;
				Canvas.SetLeft(_getParentCtrl().noEntryMarker, ptSign.X);
				Canvas.SetTop(_getParentCtrl().noEntryMarker, ptSign.Y);

				// Make sure the marker is visible
				if (_getParentCtrl().noEntryMarker.Visibility != System.Windows.Visibility.Visible)
					_getParentCtrl().noEntryMarker.Visibility = System.Windows.Visibility.Visible;

			}
			else
			{

				// If need to hide sign...
				if (_getParentCtrl().noEntryMarker.Visibility != System.Windows.Visibility.Collapsed)
					_getParentCtrl().noEntryMarker.Visibility = System.Windows.Visibility.Collapsed;

			}

		}

		private void _lowLevel_scrollRowIntoView(int iRowIndex)
		{

			// Check the grid height
			_lowLevel_updatePlannerGridHeight();

			// If scrolling to bottom...
			if (iRowIndex == (gridPlanner.RowDefinitions.Count - 1))
				_getParentCtrl().scrollViewer.ScrollToBottom();
			else
			{

				// Find the min and max offset for the item to be visible
				double dblMinOffset = Math.Max(0, ((iRowIndex + 1) * RowHeight) - _getParentCtrl().scrollViewer.ViewportHeight);
				double dblMaxOffset = (iRowIndex * RowHeight);

				// If item is not visible...
				if (
					(_getParentCtrl().scrollViewer.VerticalOffset < dblMinOffset)
					||
					(_getParentCtrl().scrollViewer.VerticalOffset > dblMaxOffset)
				)
				{

					// Find the nearest scroll position
					if (Math.Abs(_getParentCtrl().scrollViewer.VerticalOffset - dblMinOffset) < Math.Abs(_getParentCtrl().scrollViewer.VerticalOffset - dblMaxOffset))
						_getParentCtrl().scrollViewer.ScrollToVerticalOffset(dblMinOffset);
					else
						_getParentCtrl().scrollViewer.ScrollToVerticalOffset(dblMaxOffset);

				}

			}

		}

		private void _lowLevel_updatePlannerGridHeight()
		{

			// Calculate expected height of grid
			double dblHeight = gridPlanner.RowDefinitions.Count * RowHeight;

			// If height needs changing...
			if (gridPlanner.Height != dblHeight)
			{

				// Update the height of the grid
				gridPlanner.Height = dblHeight;
				this.Height = gridPlanner.Height;

				// Recalc layout
				_getParentCtrl().scrollViewer.UpdateLayout();

			}

		}

		private List<ActionBoingCtrl> _lowLevel_getAllActionBoingCtrls()
		{

			// Declare return variable
			List<ActionBoingCtrl> Ftmp = new List<ActionBoingCtrl>();

			// All boings must have events plugged in
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not an action
				if (!(child is ActionBoingCtrl))
					continue;
				ActionBoingCtrl action = child as ActionBoingCtrl;

				// Add to list
				Ftmp.Add(action);

			}

			// Return variable
			return Ftmp;

		}

		private void _lowLevel_startDragging(ActionBoingCtrl actionBoing, bool bNew)
		{
#if DEBUG
			Debug.Assert(m_dragBoing == null);
			Debug.Assert(m_dragMode == eDragMode.NotSet);
#endif // #if DEBUG

			// Initialize drag members
			m_dragBoing = actionBoing;
			m_dragBoing.Opacity = 0.3;
			m_dragMode = bNew ? eDragMode.DraggingNewBoing : eDragMode.DraggingBoing;

			// Show leg
			m_dragBoing.ShowLeg = true;
			m_dragBoing.SetLegRows(m_dragBoing.UnderlyingAction.DaySpan - 1, false);

			// Remove from the planner
			if(m_dragMode == eDragMode.DraggingBoing)
				gridPlanner.Children.Remove(m_dragBoing);

			// Add new boing to main window drag canvas
			_getParentCtrl().dragCanvas.Children.Add(m_dragBoing);

			// Before showing drag canvas, hide the children
			foreach (UIElement el in _getParentCtrl().dragCanvas.Children)
			{
				if(el != m_dragBoing)
					el.Visibility = System.Windows.Visibility.Collapsed;
			}
			_getParentCtrl().dragCanvas.Visibility = System.Windows.Visibility.Visible;

			// Capture the mouse
			gridPlanner.CaptureMouse();

		}

		private void _lowLevel_removeRow(int iRowIndex)
		{
#if DEBUG
			Debug.Assert(_lowLevel_isRowEmpty(iRowIndex));
#endif // #if DEBUG

			// Get the slot for this day
			Slot slot = _lowLevel_getSlotAtRowIndex(iRowIndex);

			// Pull up all the actions on the same day
			int iRowIndex2 = iRowIndex;
			while (iRowIndex2 < gridPlanner.RowDefinitions.Count)
			{

				// Get the slot below
				Slot slotBelow = _lowLevel_getSlotAtRowIndex(iRowIndex2 + 1);

				// If part of the same day...
				if (slotBelow.Day == slot.Day)
				{

					// Copy the action below upwards
					ActionBoingCtrl actionBelow = _lowLevel_getBoingAtRowIndex(iRowIndex2 + 1);
#if DEBUG
					Debug.Assert(actionBelow != null);
#endif // #if DEBUG
					Grid.SetRow(actionBelow, iRowIndex2);

				}
				else
					break;

				// Move on
				iRowIndex2++;

			} // while-loop

			// Remove the remaining empty slot and all children
			List<FrameworkElement> controls = _lowLevel_findControlsInRow(iRowIndex2, 1, true);
			for (int i1 = 0; i1 < controls.Count; i1++ )
				gridPlanner.Children.Remove(controls[i1]);
#if DEBUG
			Debug.Assert(_lowLevel_isRowEmpty(iRowIndex2));
#endif // #if DEBUG
			gridPlanner.RowDefinitions.RemoveAt(iRowIndex2);

			// Decrement the grid.row assignments
			_lowLevel_incRowAssignments(iRowIndex2 + 1, false);

			// Reduce height of grid
			_lowLevel_updatePlannerGridHeight();

			// Update the shape row-span
			_lowLevel_managePayDayMarker();

		}

		private bool _lowLevel_hasSiblingRows(int iRowIndex)
		{

			// Declare return variable
			bool bFtmp = false;

			// Get the slots for this row, above and below rows
			Slot slotAbove = null;
			Slot slotAtIndex = null;
			Slot slotBelow = null;
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not a grid
				if (!(child is Grid))
					continue;
				Grid grid = child as Grid;

				// If this is the day grid...
				if (
					(Grid.GetColumn(grid) == 0)
					&&
					(grid.Tag != null)
				)
				{

					// Update rows
					int iRow = Grid.GetRow(grid);
					if (iRow == (iRowIndex - 1))
						slotAbove = grid.Tag as Slot;
					else if (iRow == iRowIndex)
						slotAtIndex = grid.Tag as Slot;
					else if (iRow == (iRowIndex + 1))
						slotBelow = grid.Tag as Slot;
					
				}

			}
#if DEBUG
			Debug.Assert(slotAtIndex != null);
#endif // #if DEBUG

			// Look for matching days
			if (
				((slotAbove != null) && (slotAbove.Day == slotAtIndex.Day))
				||
				((slotBelow != null) && (slotBelow.Day == slotAtIndex.Day))
			)
				bFtmp = true;

			// Return variable
			return bFtmp;

		}

		private Dictionary<Slot, ActionBoingCtrl> _lowLevel_getSlotActionMap()
		{

			// Declare return variable
			Dictionary<Slot, ActionBoingCtrl> Ftmp = new Dictionary<Slot, ActionBoingCtrl>();

			// Build list of slots and boings
			Slot[] slots = new Slot[gridPlanner.RowDefinitions.Count];
			ActionBoingCtrl[] boings = new ActionBoingCtrl[gridPlanner.RowDefinitions.Count];
			foreach (UIElement child in gridPlanner.Children)
			{

				// Ignore if not a framework element
				if (!(child is FrameworkElement))
					continue;
				FrameworkElement el = child as FrameworkElement;

				// Get the row and column index
				int iRowIndex = Grid.GetRow(el);
				int ColumnIndex = Grid.GetColumn(el);

				// If this is the day grid...
				if(
					(ColumnIndex == 0)
					&&
					(el is Grid)
				)
				{
#if DEBUG
					Debug.Assert(slots[iRowIndex] == null);
#endif // #if DEBUG
					slots[iRowIndex] = (el as Grid).Tag as Slot;
				}
				else if(
					(ColumnIndex == 1)
					&&
					(el is ActionBoingCtrl)
				)
				{
#if DEBUG
					Debug.Assert(boings[iRowIndex] == null);
#endif // #if DEBUG
					boings[iRowIndex ] = el as ActionBoingCtrl;
				}

			}

			// Build map from arrays
			for (int iRowIndex = 0; iRowIndex < gridPlanner.RowDefinitions.Count; iRowIndex++)
			{
#if DEBUG
				Debug.Assert(slots[iRowIndex] != null);
#endif // #if DEBUG
				Ftmp.Add( slots[iRowIndex], boings[iRowIndex] );
			}

			// Return variable
			return Ftmp;

		}

		private void _lowLevel_applyIndentationAndLegLengths()
		{

			int iTemp = 0;

			// get all slots (check slots are all present)
			Dictionary<Slot, ActionBoingCtrl> slotActionMap = _lowLevel_getSlotActionMap();

			// Debug dump
#if DEBUG
			Debug.WriteLine("_lowLevel_applyIndentationAndLegLengths");
			for (int iRowIndex = 0; iRowIndex < slotActionMap.Count; iRowIndex++)
			{

				// Get element as row index
				Slot slot = slotActionMap.ElementAt(iRowIndex).Key;
				ActionBoingCtrl actionCtrl = slotActionMap.ElementAt(iRowIndex).Value;

				// Dump row details
				Debug.WriteLine("Row {0} Day {1}.{2} DaySpan {3}",
					iRowIndex,
					slot.Day,
					slot.Offset,
					actionCtrl == null ? 0 : actionCtrl.UnderlyingAction.DaySpan
				);

			}
#endif // #if DEBUG

			// Build indentation mask
			int[] iMask = new int[slotActionMap.Count];
			for (int iRowIndex = 0; iRowIndex < slotActionMap.Count; iRowIndex++)
			{

				// Get element as row index
				Slot slot = slotActionMap.ElementAt(iRowIndex).Key;
				ActionBoingCtrl actionCtrl = slotActionMap.ElementAt(iRowIndex).Value;

				// If there is something in this slot...
				if (actionCtrl != null)
				{

					// Get day span of action
					int iDaySpan = actionCtrl.UnderlyingAction.DaySpan;

					// Calculate the leg rows
					int iLegRows = 0;
					if (iDaySpan > 1)
					{
						int iFinalDay = slot.Day + iDaySpan - 1;
						while (
							((iRowIndex + 1 + iLegRows) < gridPlanner.RowDefinitions.Count)
							&&
							(slotActionMap.ElementAt(iRowIndex + 1 + iLegRows).Key.Day <= iFinalDay)
						)
							iLegRows++;
					}
					actionCtrl.SetLegRows(iLegRows,false);

					// Calculate the indentation for this action
					int iIndentation = 0;
					iTemp = iMask[iRowIndex];
					while (iTemp != 0)
					{
						iTemp >>= 1;
						iIndentation++;
					}

					// Check the indentation
					int iIndentation_px = (iIndentation == 0) ? 0 : iIndentation * (ActionBoingCtrl.LegThickness + 4);
					TranslateTransform tt = actionCtrl.RenderTransform as TranslateTransform;
					if (tt.X != iIndentation_px)
						tt.X = iIndentation_px;

					// Apply the indentation of this action to the proceeding rows
					if (iLegRows > 0)
					{

						// apply the indentation
						for (int i1 = 0; i1 < iLegRows; i1++)
						{
							if( (iRowIndex + 1 + i1) < gridPlanner.RowDefinitions.Count)
								iMask[iRowIndex + 1 + i1] |= (1 << iIndentation);
						}

					}

				}

				// Debug dump
#if DEBUG
				Debug.WriteLine("Row {0} Day {1} DaySpan {2}  IndentationMask={3}",
					iRowIndex,
					slot.Day,
					actionCtrl == null ? 0 : actionCtrl.UnderlyingAction.DaySpan,
					iMask[iRowIndex]
				);
#endif // #if DEBUG

			}

		}

		private bool _lowLevel_updateSelection(ActionBoingCtrl action)
		{

			// Return true when the selection has changed
			bool bFtmp = false;

			// If selection reference has changed...
			if (action != m_selected)
			{

				// Clear current selection
				if (m_selected != null)
				{
					m_selected.IsSelected = false;
					bFtmp = true;
				}

				// Set new selection
				m_selected = action;
				if (m_selected != null)
				{
					m_selected.IsSelected = true;
					bFtmp = true;
				}

			}

			// Return variable
			return bFtmp;

		}


#if DEBUG
		private void _lowLevel_assertValidContent()
		{

			// get the row map
			Dictionary<Slot, ActionBoingCtrl> slotActionMap = _lowLevel_getSlotActionMap();

			// Check selection
			bool bSelectionFound = false;
			for (int iRowIndex = 0; iRowIndex < slotActionMap.Count; iRowIndex++)
			{

				// Get the action at this row
				ActionBoingCtrl action = slotActionMap.ElementAt(iRowIndex).Value;
				if(action==null)
					continue;

				// Check selection flag
				Debug.Assert(action.IsSelected == (action==m_selected));

				// If this is the selection...
				if(m_selected == action)
				{
					Debug.Assert(!bSelectionFound);
					bSelectionFound = true;
				}

			} // for-loop
			Debug.Assert( (m_selected!=null) == (bSelectionFound) );

			// Check that all rows contain correctly sequenced slots
			int iCurrentDay = 0;
			int iCurrentOffset = 0;
			for (int iRowIndex = 0; iRowIndex < slotActionMap.Count; iRowIndex++)
			{

				// Get the slot for this row
				Slot slot = slotActionMap.ElementAt(iRowIndex).Key;

				// Debug dump
				Debug.WriteLine("RowIndex {0}  Day {1} Offset {2}", iRowIndex, slot.Day, slot.Offset);

				// Cannot have an empty non-day slot
				if (slot.Offset > 0)
					Debug.Assert(slotActionMap[slot] != null);

				// If slot is the same day...
				if (slot.Day == iCurrentDay)
				{

					// The offset must follow
					Debug.Assert(slot.Offset == (iCurrentOffset + 1));

				}
				else
				{

					// Must be the next day
					Debug.Assert(slot.Day == (iCurrentDay + 1));
					Debug.Assert(slot.Offset == 0);

				}

				// Update tracker variables
				iCurrentDay = slot.Day;
				iCurrentOffset = slot.Offset;

			} // for-loop

			// Check the ids are all unique and valid
			List<WorkflowAction_Base> actions = new List<WorkflowAction_Base>();
			for (int iRowIndex = 0; iRowIndex < slotActionMap.Count; iRowIndex++)
			{

				// Get the action at this row
				ActionBoingCtrl action = slotActionMap.ElementAt(iRowIndex).Value;
				if (action != null)
					actions.Add(action.UnderlyingAction);

			}
			foreach (WorkflowAction_Base action in actions)
			{
				Debug.Assert(action.Identifier != Guid.Empty);
				foreach (WorkflowAction_Base action2 in actions)
				{
					if (action == action2)
						continue;
					if (action.Identifier == action2.Identifier)
						Debug.Assert(false);
				}
			}

		}
#endif // #if DEBUG

		#endregion // Low level row operations

	}

}
